﻿namespace SenenFernandez.SmartImageComparer
{
    using SenenFernandez.SmartExtensionMethods;
    using SenenFernandez.SmartExtensionMethods.Interactivity;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Linq;
    using System.IO;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Win32;

    public class MainViewModel : NotificationObject
    {
        private BackgroundWorker worker;
        private ICommand selectImageCommand;
        private ICommand selectDirectoryCommand;
        private ICommand compareCommand;
        private ICommand copyCommand;
        private ICommand closeCommand;
        private ICommand dragWindowCommand;

        public string Title { get; private set; }

        public bool IsReadyToWork { get; private set; }

        public int CurrentProgress { get; set; }

        public string ImagePath { get; set; }

        public ImageSource OriginalImage
        {
            get
            {
                ImageSource imageSource = null;
                if (!string.IsNullOrEmpty(ImagePath))
                {
                    imageSource = ImagePath.ToImageSource();
                }
                return imageSource;
            }
        }

        public ImageSource SearchImage { get; private set; }

        public string SearchDirectory { get; set; }

        public IEnumerable<string> Duplicates { get; private set; }

        public ICommand SelectImageCommand
        {
            get { return selectImageCommand ?? (selectImageCommand = new DelegateCommand(OnSelectImage)); }
        }

        public ICommand SelectDirectoryCommand
        {
            get { return selectDirectoryCommand ?? (selectDirectoryCommand = new DelegateCommand(OnSelectDirectory)); }
        }

        public ICommand CompareCommand
        {
            get { return compareCommand ?? (compareCommand = new DelegateCommand(OnCompare)); }
        }

        public ICommand CopyCommand
        {
            get { return copyCommand ?? (copyCommand = new DelegateCommand<IList>(OnCopy)); }
        }

        public ICommand CloseCommand
        {
            get { return closeCommand ?? (closeCommand = new DelegateCommand<Window>(OnClose)); }
        }

        public ICommand DragWindowCommand
        {
            get { return dragWindowCommand ?? (dragWindowCommand = new DelegateCommand<Window>(OnDragWindow)); }
        }

        public MainViewModel()
        {
            worker = new BackgroundWorker
            {
                WorkerReportsProgress = true
            };
            worker.DoWork += DoCompare;
            worker.RunWorkerCompleted += RunWorkerCompleted;
            worker.ProgressChanged += OnProgressChanged;
            SetValue(() => SearchDirectory, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
            Duplicates = new List<string>();
            SetDefaultValues();
        }

        private void SetDefaultValues()
        {
            SetValue(() => Title, Properties.Resources.ApplicationTitle);
            SetValue(() => IsReadyToWork, true);
        }

        private void OnSelectImage()
        {
            var dialog = new OpenFileDialog
            {
                FileName = ImagePath,
                Filter = Properties.Resources.OpenFileDialogFilter,
            };
            if (dialog.ShowDialog().GetValueOrDefault())
            {
                SetValue(() => ImagePath, dialog.FileName);
                SetValue(() => SearchDirectory, Path.GetDirectoryName(ImagePath));
                RaisePropertyChanged(() => OriginalImage);
            }
        }

        private void OnSelectDirectory()
        {
            var dialog = new System.Windows.Forms.FolderBrowserDialog
            {
                ShowNewFolderButton = false,
            };
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                SetValue(() => SearchDirectory, dialog.SelectedPath);
            }
        }

        private void OnCompare()
        {
            try
            {
                Mouse.OverrideCursor = Cursors.Wait;
                SetValue(() => IsReadyToWork, false);
                SetValue(() => Title, string.Format(Properties.Resources.Working, Properties.Resources.ApplicationTitle));
                SetValue(() => CurrentProgress, 0);
                worker.RunWorkerAsync(new Diff(ImagePath, SearchDirectory));
            }
            catch (Exception exception)
            {
                exception.Show();
            }
        }

        private void OnCopy(IList selectedItems)
        {
            if (selectedItems != null)
            {
                var sb = new StringBuilder();
                foreach (string item in selectedItems)
                {
                    sb.AppendLine(item);
                }
                Clipboard.SetText(sb.ToString());
            }
        }

        private void OnClose(Window window)
        {
            if (window != null)
            {
                window.Close();
            }
        }

        private void OnDragWindow(Window window)
        {
            if (window != null)
            {
                window.DragMove();
                if (window.WindowState == WindowState.Maximized)
                {
                    window.WindowState = WindowState.Normal;
                }
            }
        }
        
        private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            SetValue(() => CurrentProgress, e.ProgressPercentage);
            SetValue(() => SearchImage, e.UserState.ToString().ToImageSource());
        }

        private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                SetValue(() => Duplicates, e.Result as IEnumerable<string>);
                SetDefaultValues();
                Mouse.OverrideCursor = null;
            }
            catch (Exception exception)
            {
                exception.Log();
            }
        }

        private void DoCompare(object sender, DoWorkEventArgs e)
        {
            var diff = e.Argument as Diff;
            if (diff == null || !File.Exists(diff.Source) || !Directory.Exists(diff.Destination))
            {
                return;
            }
            var recursive = true;
            var files = GetFiles(diff.Destination, recursive,
                file =>
                    file != diff.Source &&
                    file.ToUpperOrEmpty().EndsWith(Path.GetExtension(diff.Source).ToUpperOrEmpty()));
            var totalFiles = files.Count;
            int count = 0;
            var duplicates = new List<string>();
            var original = new Bitmap(diff.Source);
            foreach (string file in files)
            {
                var duplicate = new Bitmap(file);
                bool found = original.Width == duplicate.Width && original.Height == duplicate.Height;
                if (found)
                {
                    for (int x = 0; x < original.Width; x++)
                    {
                        for (int y = 0; y < original.Height; y++)
                        {
                            if (original.GetPixel(x, y) != duplicate.GetPixel(x, y))
                            {
                                found = false;
                                break;
                            }
                        }
                    }
                }
                if (found)
                {
                    duplicates.Add(file);
                }
                worker.ReportProgress(Percentage(++count, totalFiles), file);
            }
            e.Result = duplicates;
        }

        private int Percentage(int actual, int total)
        {
            return (actual * 100) / total;
        }

        private List<string> GetDirectories(string path, bool searchAllDirectories = false, Predicate<string> filter = null)
        {
            var list = new List<string>();
            try
            {
                var directoryInfo = new DirectoryInfo(path);
                Parallel.ForEach(directoryInfo.GetDirectories(), folder =>
                {
                    try
                    {
                        if (filter == null || filter(folder.FullName))
                        {
                            list.Add(folder.FullName);
                            if (searchAllDirectories)
                            {
                                list.AddRange(GetDirectories(folder.FullName, true, filter));
                            }
                        }
                    }
                    catch (UnauthorizedAccessException unauthorizedAccessException)
                    {
                        unauthorizedAccessException.Log();
                    }
                    catch (Exception exception)
                    {
                        exception.Log();
                    }
                });
            }
            catch (Exception exception)
            {
                exception.Log();
            }
            return list;
        }

        private List<string> GetFiles(string path, bool searchAllDirectories = false, Predicate<string> filter = null)
        {
            var list = new List<string>();
            try
            {
                var directories = new List<string>
                {
                    path
                };
                if (searchAllDirectories)
                {
                    directories.AddRange(GetDirectories(path, true, null));
                }
                Parallel.ForEach(directories, folder =>
                {
                    try
                    {
                        var directory = new DirectoryInfo(folder);
                        foreach (var file in directory.GetFiles())
                        {
                            try
                            {
                                if (filter == null || filter(file.FullName))
                                {
                                    list.Add(file.FullName);
                                }
                            }
                            catch (Exception exception)
                            {
                                exception.Log();
                            }
                        }
                    }
                    catch (UnauthorizedAccessException unauthorizedAccessException)
                    {
                        unauthorizedAccessException.Log();
                    }
                    catch (Exception exception)
                    {
                        exception.Log();
                    }
                });
            }
            catch (Exception exception)
            {
                exception.Log();
            }
            return list;
        }
    }
}